package com.google.code.wicket.auth.component.marker;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.wicket.Component;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.Session;

/**
 * An extension of {@link MarkerSupport} that stores missing privileges per
 * component type in the session.
 * 
 * @param <S>
 *            the session type
 * @param <T>
 *            the privilege type
 */
public abstract class SessionBasedMarkerSupport<S extends Session, T> extends
        MarkerSupport<T> {

    /**
     * A Metadata key for a map of component type and missing privileges. We
     * must use {@link HashMap} as generic type since {@link Map} does not
     * extend {@link Serializable}. Grrr.
     */
    private class MapMetaDataKey extends
            MetaDataKey<HashMap<Class<? extends Component>, Collection<T>>> {

        private static final long serialVersionUID = 3552227108892736237L;
        private final Class<T> privilegeType;

        public MapMetaDataKey(final Class<T> privilegeType) {
            this.privilegeType = privilegeType;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(final Object obj) {
            boolean result;
            if (obj instanceof SessionBasedMarkerSupport<?, ?>.MapMetaDataKey) {
                final MapMetaDataKey other = (MapMetaDataKey) obj;
                result = new EqualsBuilder() //
                        .append(this.privilegeType, other.privilegeType) //
                        .isEquals();
            } else {
                result = false;
            }
            return result;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return new HashCodeBuilder() //
                    .append(this.privilegeType) //
                    .toHashCode();
        }

    }

    private final MetaDataKey<HashMap<Class<? extends Component>, Collection<T>>> mapMetaDataKey;

    /**
     * @param privilegeType
     */
    public SessionBasedMarkerSupport(final Class<T> privilegeType) {
        super(privilegeType);
        this.mapMetaDataKey = new MapMetaDataKey(privilegeType);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void markType(final Class<? extends Component> componentClass,
            final Collection<T> missingValues) {
        this.getSessionMap().put(componentClass, missingValues);
    }

    private Map<Class<? extends Component>, Collection<T>> getSessionMap() {
        final S session = this.getSession();
        HashMap<Class<? extends Component>, Collection<T>> metaData = session
                .getMetaData(this.mapMetaDataKey);
        if (metaData == null) {
            metaData = new HashMap<Class<? extends Component>, Collection<T>>();
            session.setMetaData(this.mapMetaDataKey, metaData);
        }
        return metaData;
    }

    /**
     * Get the current session.
     * 
     * @return
     */
    @SuppressWarnings("unchecked")
    protected S getSession() {
        return (S) Session.get();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Collection<T> readValues(final Component component) {
        // try component metadata first
        final Collection<T> values = super.readValues(component);
        if (values.isEmpty()) {
            // then type metadata from session
            final Collection<T> valuesFromSession = this.getSessionMap().get(
                    component.getClass());
            if (valuesFromSession != null) {
                values.addAll(valuesFromSession);
            }
        }
        return values;
    }

}